package position

import (
	b "code.google.com/p/gochess/board"
	"fmt"
)

// A type which satisfies MoveMaker can be used for MakeMove/UnmakeMove.
type MoveMaker interface {
	Get(b.Square) Piece
	Set(Piece, b.Square)
	Remove(b.Square) int8
	Reset(Piece, b.Square, int8)
	EnPassantTarget() b.Square
	SetEnPassantTarget(b.Square)
	HalfmoveClock() int32
	SetHalfmoveClock(int32)
	CastlingRights() CastlingRights
	SetCastlingRights(CastlingRights)
	SideToMove() Color
	SwitchSides()
	Move(b.Square, b.Square)
}

/*
move 1-21
captureIndex 22-25
CapturedPiece 26-30
EnPassantSquare 31-38
CastlingRights 39-42
promotionIndex 43-46
halfmoveClock 47-54
*/

//
// MoveUnmaking stores all necessary informations for taking back a move.
type MoveUnmaking uint64

const (
	offsetCaptureIndex    = BitSizeMove
	offsetCapturedPiece   = offsetCaptureIndex + bitSizeSquareListIndex
	offsetEnPassantTarget = offsetCapturedPiece + bitSizePiece
	offsetCastlingRights  = offsetEnPassantTarget + b.BitSizeSquare
	offsetPromotionIndex  = offsetCastlingRights + bitSizeCastlingRights
	offsetHalfmoveClock   = offsetPromotionIndex + bitSizeHalfmoveClock
)

// NewMoveUnmaking returns a MoveUnmaking object with the given parameters.
func NewMoveUnmaking(move Move, capturedPiece Piece, epTarget b.Square,
	halfmoveClock int32, castlingRights CastlingRights, captureIndex, promotionIndex int8) MoveUnmaking {

	return MoveUnmaking(move) |
		MoveUnmaking(captureIndex)<<offsetCaptureIndex |
		MoveUnmaking(capturedPiece)<<offsetCapturedPiece |
		MoveUnmaking(epTarget)<<offsetEnPassantTarget |
		MoveUnmaking(castlingRights)<<offsetCastlingRights |
		MoveUnmaking(promotionIndex)<<offsetPromotionIndex |
		MoveUnmaking(halfmoveClock)<<offsetHalfmoveClock
}

// Move returns the Move to take back.
func (this MoveUnmaking) Move() Move {
	return Move(this & BitMaskMove)
}

// CaptureIndex returns the piece list index of a captured piece.
// In case of a non capturing move 0 is returned.
func (this MoveUnmaking) CaptureIndex() int8 {
	return int8(this >> offsetCaptureIndex & bitMaskSquareListIndex)
}

// CapturedPiece returns the captured piece.
// In case of a non capturing move NoPiece is returned.
func (this MoveUnmaking) CapturedPiece() Piece {
	return Piece(this >> offsetCapturedPiece & bitMaskPiece)
}

// EnPassantSquare returns the en passant square of the position
// before the move was made.
func (this MoveUnmaking) EnPassantTarget() b.Square {
	return b.Square(this >> offsetEnPassantTarget & b.BitMaskSquare)
}

// CastlingRights returns the castling rights of the position
// before the move was made.
func (this MoveUnmaking) CastlingRights() CastlingRights {
	return CastlingRights(this >> offsetCastlingRights & bitMaskCastlingRights)
}

// PromotionIndex returns the piece list index of a promoted pawn.
// In case of a non promotion 0 is returned.
func (this MoveUnmaking) PromotionIndex() int8 {
	return int8(this >> offsetPromotionIndex & bitMaskSquareListIndex)
}

// HalfmoveClock returns the halfmove Clock of the position
// before the move was made.
func (this MoveUnmaking) HalfmoveClock() int32 {
	return int32(this >> offsetHalfmoveClock & bitMaskHalfmoveClock)
}

func (this MoveUnmaking) String() string {

	if this == 0 {
		return "{ZeroUnmaking}"
	}
	return fmt.Sprintf("{Move: %v, captured: %v, epTarget: %v, castling rights: %v, halfmove clock: %v, capture idx: %v, promo idx: %v}",
		this.Move(), this.CapturedPiece(), this.EnPassantTarget(), this.CastlingRights(), this.HalfmoveClock(), this.CaptureIndex(), this.PromotionIndex())
}

// MakeMove executes the move in the given position and
// updates the status informations of the position.
// It returns a MoveUnmaking object, which can be used to take back the move.
func MakeMove(p MoveMaker, move Move) MoveUnmaking {
	start, dst := move.Start(), move.Destination()
	halfmoveClock := p.HalfmoveClock() + 1
	captured := p.Get(dst)
	var captureIdx int8
	cr := p.CastlingRights()
	// deal with captures
	if captured != NoPiece {
		captureIdx = p.Remove(dst)
		halfmoveClock = 0
		if cr != NoCastlingRights && captured.Type() == Rook {
			opponent := Opponent(p.SideToMove())
			switch dst {
			case Castlings[opponent][CastlingShort].RookLocation:
				// color spoiled short castling for opponent
				p.SetCastlingRights(cr.Remove(opponent, CastlingShort))
			case Castlings[opponent][CastlingLong].RookLocation:
				// color spoiled long castling for opponent
				p.SetCastlingRights(cr.Remove(opponent, CastlingLong))
			}
		}
	}
	var epTarget b.Square
	var promoIdx int8
	moved := p.Get(start)
	if moved.Type() == Pawn {
		// pawn moves
		halfmoveClock = 0
		switch {
		case move.CapturesEnPassant():
			captureIdx = p.Remove(dst - MoveDirection(p.SideToMove()))
		case move.Promotion() != NoPieceType:
			promoIdx = p.Remove(start)
			p.Set(NewPiece(p.SideToMove(), move.Promotion()), start)
		case dst-start == MoveDirection(p.SideToMove())<<1:
			epTarget = start + MoveDirection(p.SideToMove())
		}
	} else {
		// no pawn move
		if move.Castling() != NoCastling {
			castling := &Castlings[p.SideToMove()][move.Castling()]
			// move the king first, as the rook move is saved in the move
			p.Move(castling.KingLocation, castling.KingDestination)
			p.SetCastlingRights(p.CastlingRights().Remove(p.SideToMove(), CastlingBoth))
		} else {
			// check if stm looses castling rights
			if p.CastlingRights() != NoCastlingRights {
				switch {
				case moved.Type() == King:
					p.SetCastlingRights(p.CastlingRights().Remove(p.SideToMove(), CastlingBoth))
				case moved.Type() == Rook:
					switch start {
					case Castlings[p.SideToMove()][CastlingLong].RookLocation:
						p.SetCastlingRights(p.CastlingRights().Remove(p.SideToMove(), CastlingLong))
					case Castlings[p.SideToMove()][CastlingShort].RookLocation:
						p.SetCastlingRights(p.CastlingRights().Remove(p.SideToMove(), CastlingShort))
					}
				}
			}
		}
	}
	unmake := NewMoveUnmaking(move, captured, p.EnPassantTarget(), p.HalfmoveClock(),
		cr, captureIdx, promoIdx)
	p.SetHalfmoveClock(halfmoveClock)
	p.SetEnPassantTarget(epTarget)
	p.SwitchSides()
	p.Move(start, dst)
	return unmake
}

// UnmakeMove takes back a move for the given position.
// It uses the given MoveUnmaking value to reset the position
// to the state before the move was made.
func UnmakeMove(p MoveMaker, uMove MoveUnmaking) {
	p.SwitchSides()
	move := uMove.Move()
	if move.IsSpecial() {
		switch {
		case move.Promotion() != NoPieceType:
			p.Remove(move.Destination())
			p.Reset(NewPiece(p.SideToMove(), Pawn), move.Destination(), uMove.PromotionIndex())
		case move.Castling() != NoCastling:
			p.Move(Castlings[p.SideToMove()][move.Castling()].KingDestination,
				Castlings[p.SideToMove()][move.Castling()].KingLocation)
		case move.CapturesEnPassant():
			p.Reset(NewPiece(Opponent(p.SideToMove()), Pawn),
				uMove.EnPassantTarget()-MoveDirection(p.SideToMove()), uMove.CaptureIndex())
		}
	}
	p.Move(move.Destination(), move.Start())
	if uMove.CapturedPiece() != NoPiece {
		p.Reset(uMove.CapturedPiece(), move.Destination(), uMove.CaptureIndex())
	}
	p.SetCastlingRights(uMove.CastlingRights())
	p.SetHalfmoveClock(uMove.HalfmoveClock())
	p.SetEnPassantTarget(uMove.EnPassantTarget())
}
